package vg.modules.navigator.components;

import com.google.common.collect.Lists;
import tzhomestudio.framework.shared.gui.JTreeUtils;
import tzhomestudio.framework.shared.utils.FileUtils;
import vg.interfaces.ll_services.data_base_service.data.header.*;
import vg.services.main_manager.VGMainServiceHelper;

import javax.swing.*;
import javax.swing.event.TreeExpansionEvent;
import javax.swing.event.TreeWillExpandListener;
import javax.swing.tree.*;
import java.awt.*;
import java.beans.PropertyChangeListener;
import java.util.List;

/**
 * @author Timur Zolotuhin (e-mail: tzolotuhin@gmail.com)
 */
public class SmartTreeComponent {
    // Main components
    private JPanel outView, innerView;

    private JTree tree;

    // Main data
    private String searchRequest;

    // Mutex
    private final Object generalMutex = new Object();

    public SmartTreeComponent(int graphModelId, final PropertyChangeListener progressListener) {
        SmartTreeNode root = new SmartTreeNode(VGMainServiceHelper.graphDataBaseManager.getGraphModelHeader(graphModelId));

        outView = new JPanel(new GridLayout(1, 1));
        innerView = new JPanel(new GridBagLayout());
        outView.add(innerView);

        final DefaultTreeModel model = new DefaultTreeModel(root);
        tree = new JTree();
        tree.setModel(model);
        tree.setCellRenderer(new SmartTreeRenderer());
        tree.setShowsRootHandles(true);
        JTreeUtils.expandAll(tree, false);

        tree.addTreeWillExpandListener(new TreeWillExpandListener() {
            @Override
            public void treeWillExpand(TreeExpansionEvent event) throws ExpandVetoException {
                synchronized (generalMutex) {
                    SmartTreeNode smartTreeNode = (SmartTreeNode)event.getPath().getLastPathComponent();
                    doExpandAndSearch(smartTreeNode);
                }
            }

            @Override
            public void treeWillCollapse(TreeExpansionEvent event) throws ExpandVetoException {
                synchronized (generalMutex) {
                    SmartTreeNode smartTreeNode = (SmartTreeNode)event.getPath().getLastPathComponent();
                    smartTreeNode.removeAllChildren();
                }
            }
        });

        rebuildView();
    }

    public JPanel getView() {
        return outView;
    }

    public void search(final String searchRequest) {
        synchronized (generalMutex) {
            doSearch(searchRequest);
        }
    }

    public JTree getTree() {
        return tree;
    }

//==============================================================================
//---------------PRIVATE METHODS------------------------------------------------
    private void rebuildView() {
        innerView.removeAll();

        innerView.add(tree, new GridBagConstraints(0,0,  1,1,  1,1,  GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0),  0,0));

        innerView.updateUI();
    }

    private int recursiveSearch(String request, SmartTreeNode root) {
        int searchResults = 0;

        if (root.isLoaded()) {
            for (int index = 0; index < root.getChildCount(); index++) {
                if (root.getChildAt(index) instanceof SmartTreeNode) {
                    SmartTreeNode child = (SmartTreeNode)root.getChildAt(index);
                    searchResults += recursiveSearch(request, child);
                }
            }
        } else {
            List<SmartTreeNode> children = downloadChildren4Node(root);
            for (SmartTreeNode child : children) {
                searchResults += recursiveSearch(request, child);
            }
        }

        boolean satisfiesSearchCondition = false;
        // searching...
        if (root.isAttributeHeader()) {
            AttributeHeader attributeHeader = root.getAttributeHeader();
            if (attributeHeader.getName().contains(request) || attributeHeader.getStringValue().contains(request)) {
                satisfiesSearchCondition = true;
                searchResults++;
            }
        }

        // setup result values
        root.setSatisfiesSearchCondition(satisfiesSearchCondition);
        root.setSearchNumber(searchResults);

        return searchResults;
    }

    private void recursiveClearSearch(SmartTreeNode root) {
        if (root.isLoaded()) {
            for (int index = 0; index < root.getChildCount(); index++) {
                if (root.getChildAt(index) instanceof SmartTreeNode) {
                    SmartTreeNode child = (SmartTreeNode)root.getChildAt(index);
                    recursiveClearSearch(child);
                }
            }
        }

        // setup result values
        root.setSatisfiesSearchCondition(false);
        root.setSearchNumber(0);
    }

    private void doExpandAndSearch(final SmartTreeNode root) {
        if (root.isLoaded())
            return;

        // download node
        SwingWorker<List<SmartTreeNode>, Void> worker = new SwingWorker<List<SmartTreeNode>, Void>() {
            @Override
            protected List<SmartTreeNode> doInBackground() throws Exception {
                synchronized (generalMutex) {
                    return downloadChildren4Node(root);
                }
            }

            @Override
            protected void done() {
                synchronized (generalMutex) {
                    try {
                        root.removeAllChildren();
                        root.setChildren(get());
                        //model.nodeStructureChanged(smartTreeNode);

                        doSearch(searchRequest);
                    } catch (Exception ex) {
                        VGMainServiceHelper.logger.printError("Unknown error", ex);
                    }
                }
            }
        };
//        if (progressListener != null) {
//            worker.getPropertyChangeSupport().addPropertyChangeListener("progress", progressListener);
//        }
        worker.execute();
    }

    private void doSearch(final String searchRequest) {
        this.searchRequest = searchRequest;

        SwingWorker<List<SmartTreeNode>, Void> worker = new SwingWorker<List<SmartTreeNode>, Void>() {
            @Override
            protected List<SmartTreeNode> doInBackground() throws Exception {
                synchronized (generalMutex) {
                    if (searchRequest == null || searchRequest.isEmpty())
                        recursiveClearSearch((SmartTreeNode)tree.getModel().getRoot());
                    else
                        recursiveSearch(searchRequest, (SmartTreeNode)tree.getModel().getRoot());
                    return null;
                }
            }

            @Override
            protected void done() {
                synchronized (generalMutex) {
                    tree.updateUI();
                }
            }
        };
        worker.execute();
    }

    private static List<SmartTreeNode> downloadChildren4Node(SmartTreeNode smartTreeNode) {
        List<SmartTreeNode> children = Lists.newArrayList();
        if (smartTreeNode.isGraphModelHeader()) {
            List<GraphHeader> graphHeaders = VGMainServiceHelper.graphDataBaseManager.getRootGraphHeadersByGraphModelId(smartTreeNode.getGraphModelHeader().getId());
            for (GraphHeader graphHeader : graphHeaders) {
                children.add(new SmartTreeNode(graphHeader));
            }
        }

        if (smartTreeNode.isGraphHeader()) {
            List<VertexHeader> vertexHeaders = VGMainServiceHelper.graphDataBaseManager.getVertexHeadersByGraphId(smartTreeNode.getGraphHeader().getId(), -1);
            for (VertexHeader vertexHeader : vertexHeaders) {
                children.add(new SmartTreeNode(vertexHeader));
            }

            List<EdgeHeader> edgeHeaders = VGMainServiceHelper.graphDataBaseManager.getEdgeHeadersByGraphId(smartTreeNode.getGraphHeader().getId(), -1);
            for (EdgeHeader edgeHeader : edgeHeaders) {
                children.add(new SmartTreeNode(edgeHeader));
            }
        }

        if (smartTreeNode.isVertexHeader()) {
            List<AttributeHeader> attributeHeaders = VGMainServiceHelper.graphDataBaseManager.getAttributeHeadersByOwner(smartTreeNode.getVertexHeader().getId(), AttributeHeader.VERTEX_OWNER_TYPE);

            if (smartTreeNode.getVertexHeader().getLink2InnerGraph() >= 0)
                children.add(new SmartTreeNode(VGMainServiceHelper.graphDataBaseManager.getGraphHeader(smartTreeNode.getVertexHeader().getLink2InnerGraph())));

            for (AttributeHeader attributeHeader : attributeHeaders) {
                children.add(new SmartTreeNode(attributeHeader));
            }
        }

        if (smartTreeNode.isEdgeHeader()) {
            List<AttributeHeader> attributeHeaders = VGMainServiceHelper.graphDataBaseManager.getAttributeHeadersByOwner(smartTreeNode.getEdgeHeader().getId(), AttributeHeader.EDGE_OWNER_TYPE);
            for (AttributeHeader attributeHeader : attributeHeaders) {
                children.add(new SmartTreeNode(attributeHeader));
            }
        }

        return children;
    }

//==============================================================================
//---------------PRIVATE CLASSES------------------------------------------------
    private static class SmartTreeRenderer extends DefaultTreeCellRenderer {
        private static final Icon graphModelIcon;
        private static final Icon graphIcon;
        private static final Icon vertexIcon;
        private static final Icon edgeIcon;

        private static final Color selectedBackgroundColor;
        private static final Color selectedForegroundColor;
        private static final Color satisfiesSearchConditionColor;

        static {
            graphModelIcon = new ImageIcon(FileUtils.getWorkingDir() + "data/resources/textures/navigator/graph_model.png");
            graphIcon = new ImageIcon(FileUtils.getWorkingDir() + "data/resources/textures/navigator/graph.png");
            vertexIcon = new ImageIcon(FileUtils.getWorkingDir() + "data/resources/textures/navigator/vertex.png");
            edgeIcon = new ImageIcon(FileUtils.getWorkingDir() + "data/resources/textures/navigator/edge.png");

            selectedBackgroundColor = javax.swing.UIManager.getDefaults().getColor("List.selectionBackground");
            selectedForegroundColor = javax.swing.UIManager.getDefaults().getColor("List.selectionForeground");
            satisfiesSearchConditionColor = Color.decode("0xFFCC00");
        }

        public Component getTreeCellRendererComponent(final JTree tree, final Object value, final boolean selected, final boolean expanded, final boolean leaf, final int row, final boolean hasFocus) {
            if (!(value instanceof SmartTreeNode)) {
                TreeCellRenderer delegate = new JTree().getCellRenderer();
                return delegate.getTreeCellRendererComponent(tree, value, selected, expanded, leaf, row, hasFocus);
            }

            SmartTreeNode smartTreeNode = (SmartTreeNode)value;

            JLabel label = new JLabel();

            if (selected) {
                label.setBackground(selectedBackgroundColor);
                label.setForeground(selectedForegroundColor);
            }

            if (smartTreeNode.isGraphModelHeader()) {
                GraphModelHeader header = smartTreeNode.getGraphModelHeader();
                label.setText("M [" + header.getId() + "] " + header.getName());
                label.setIcon(graphModelIcon);
            }

            if (smartTreeNode.isGraphHeader()) {
                GraphHeader header = smartTreeNode.getGraphHeader();
                label.setText("G [" + header.getId() + "]");
                label.setIcon(graphIcon);
            }

            if (smartTreeNode.isVertexHeader()) {
                VertexHeader header = smartTreeNode.getVertexHeader();
                label.setText("V [" + header.getId() + "]");
                label.setIcon(vertexIcon);
            }

            if (smartTreeNode.isEdgeHeader()) {
                EdgeHeader header = smartTreeNode.getEdgeHeader();
                label.setText("E [" + header.getId() + "]");
                label.setIcon(edgeIcon);
            }

            if (smartTreeNode.isAttributeHeader()) {
                AttributeHeader header = smartTreeNode.getAttributeHeader();
                label.setText("A [" + header.getId() + "] " + header.getName() + ":" + header.getStringValue());
            }

            label.setText(label.getText() + " (" + smartTreeNode.getSearchNumber() + ")");
            if (smartTreeNode.isSatisfiesSearchCondition()) {
                label.setBackground(satisfiesSearchConditionColor);
            }

            return label;
        }
    }

    public static class SmartTreeNode extends DefaultMutableTreeNode {
        private boolean loaded = false;
        private int searchNumber;
        private boolean satisfiesSearchCondition;
        private Object header;

        public SmartTreeNode(Object header) {
            setHeader(header);
            setAllowsChildren(true);

            add(new DefaultMutableTreeNode("Loading...", false));
        }

        public void setSearchNumber(int searchNumber) {
            this.searchNumber = searchNumber;
        }

        public int getSearchNumber() {
            return searchNumber;
        }

        public boolean isSatisfiesSearchCondition() {
            return satisfiesSearchCondition;
        }

        public void setSatisfiesSearchCondition(boolean satisfiesSearchCondition) {
            this.satisfiesSearchCondition = satisfiesSearchCondition;
        }

        public boolean isLoaded() {
            return loaded;
        }

        public void setHeader(Object header) {
            if (header == null) return;

            this.header = header;
        }

        public VertexHeader getVertexHeader() {
            if (header instanceof VertexHeader)
               return (VertexHeader)header;
            return null;
        }

        public GraphModelHeader getGraphModelHeader() {
            if (header instanceof GraphModelHeader)
                return (GraphModelHeader)header;
            return null;
        }

        public GraphHeader getGraphHeader() {
            if (header instanceof GraphHeader)
                return (GraphHeader)header;
            return null;
        }

        public EdgeHeader getEdgeHeader() {
            if (header instanceof EdgeHeader)
                return (EdgeHeader)header;
            return null;
        }

        public AttributeHeader getAttributeHeader() {
            if (header instanceof AttributeHeader)
                return (AttributeHeader)header;
            return null;
        }

        public boolean isGraphModelHeader() {
            return getGraphModelHeader() != null;
        }

        public boolean isGraphHeader() {
            return getGraphHeader() != null;
        }

        public boolean isVertexHeader() {
            return getVertexHeader() != null;
        }

        public boolean isEdgeHeader() {
            return getEdgeHeader() != null;
        }

        public boolean isAttributeHeader() {
            return getAttributeHeader() != null;
        }

        public void setChildren(List<SmartTreeNode> children) {
            super.removeAllChildren();
            for (SmartTreeNode node : children) {
                add(node);
            }
            loaded = true;
        }

        @Override
        public void removeAllChildren() {
            super.removeAllChildren();
            add(new DefaultMutableTreeNode("Loading...", false));
            loaded = false;
        }

        @Override
        public boolean isLeaf() {
            return isAttributeHeader();
        }
    }
}
